﻿#region Copyright (c) 2013-03, Olaf Kober <amarok.projects@gmail.com>
//================================================================================
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//	copies of the Software, and to permit persons to whom the Software is
//	furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in
//	all copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//	THE SOFTWARE.
//================================================================================
#endregion

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Amarok.Agents.Resources;


namespace Amarok.Agents
{
	/// <summary>
	/// This type represents a base class for specific agent runtime implementations.
	/// </summary>
	/// 
	/// <typeparam name="TEnvironment">
	/// The type of the runtime environment that is provided by this runtime to its hosted agents. This type
	/// must derive directly or indirectly from <see cref="RuntimeEnvironment"/>.</typeparam>
	public abstract class Runtime<TEnvironment> : CompletableObject,
		IRuntime
		where TEnvironment : RuntimeEnvironment
	{
		// data
		private readonly ManualResetEvent mShutdownCompletedSignal = new ManualResetEvent(false);
		private readonly ConcurrentStack<ISubsystem> mSubsystems = new ConcurrentStack<ISubsystem>();
		private TEnvironment mEnvironment;

		// state
		private volatile Boolean mHasStarted;


		#region ++ IRuntime Interface ++

		/// <summary>
		/// Signals the runtime to shut down. This method returns immediately without waiting for the runtime 
		/// being shut down. If the runtime shutdown has already been triggered or has already been completed, 
		/// then the method will return silently.
		/// </summary>
		/// 
		/// <param name="discardAvailableMessages">
		/// True to discard all already available messages; otherwise False to process all already available 
		/// messages to completion. Defaults to false.</param>
		public void Shutdown(Boolean discardAvailableMessages = false)
		{
			base.CompleteObject(discardAvailableMessages);
		}

		#endregion

		#region ++ Public Interface (Start, Wait, Shutdown) ++

		/// <summary>
		/// Gets a boolean value indicating whether the agent runtime has been started.
		/// </summary>
		public Boolean HasStarted
		{
			get
			{
				return mHasStarted;
			}
		}

		/// <summary>
		/// Gets a reference to the runtime environment that is provided by this runtime to its hosted agents.
		/// 
		/// It is only valid to call this property after the agent runtime has been started; otherwise an 
		/// appropriate exception will be thrown.
		/// </summary>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The agent runtime has not been started yet.</exception>
		public TEnvironment Environment
		{
			get
			{
				if (mHasStarted == false)
					throw new InvalidOperationException(RuntimeResources.RuntimeNotStarted);

				return mEnvironment;
			}
		}


		/// <summary>
		/// Starts the agent runtime.
		/// 
		/// If the agent runtime has been started before or if the agent runtime has already been shut down, 
		/// then an appropriate exception will be thrown.
		/// </summary>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The agent runtime has already been started.</exception>
		/// <exception cref="InvalidOperationException">
		/// The agent runtime is completing or has already been completed.</exception>
		/// <exception cref="RuntimeStartException">
		/// The agent runtime could not be started. Refer to the inner exception for more information.</exception>
		public void Start()
		{
			if (mHasStarted)
				throw new InvalidOperationException(RuntimeResources.RuntimeAlreadyStarted);
			if (base.IsCompleting)
				throw new InvalidOperationException(RuntimeResources.RuntimeCompletingOrCompleted);

			mHasStarted = true;

			try
			{
				// initialize
				var messageBus = CreateMessageBus();
				mEnvironment = CreateEnvironment(this, messageBus);

				// call sub-class
				OnStartup();
			}
			catch (RuntimeStartException)
			{
				throw;
			}
			catch (Exception exception)
			{
				throw new RuntimeStartException(
					RuntimeResources.RuntimeStartFailure,
					exception);
			}
		}



		/// <summary>
		/// Waits until the agent runtime has been shut down. If the agent runtime has not been started before 
		/// an appropriate exception will be thrown. If the agent runtime has already been shut down, the method
		/// will return immediately.
		/// </summary>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The agent runtime has not been started yet.</exception>
		public void Wait()
		{
			if (mHasStarted == false)
				throw new InvalidOperationException(RuntimeResources.RuntimeNotStarted);

			mShutdownCompletedSignal.WaitOne();
		}

		/// <summary>
		/// Waits until the agent runtime has been shut down. If the agent runtime has not been started before 
		/// an appropriate exception will be thrown. If the agent runtime has already been shut down, the method
		/// will return immediately.
		/// </summary>
		/// 
		/// <param name="millisecondsTimeout">
		/// The number of milliseconds to wait, or Timeout.Infinite (-1) to wait indefinitely.</param>
		/// 
		/// <returns>
		/// True if the agent runtime has shut down within the allotted time; otherwise, False.</returns>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The agent runtime has not been started yet.</exception>
		public Boolean Wait(Int32 millisecondsTimeout)
		{
			if (mHasStarted == false)
				throw new InvalidOperationException(RuntimeResources.RuntimeNotStarted);

			return mShutdownCompletedSignal.WaitOne(millisecondsTimeout);
		}

		/// <summary>
		/// Waits until the agent runtime has been shut down. If the agent runtime has not been started before 
		/// an appropriate exception will be thrown. If the agent runtime has already been shut down, the method
		/// will return immediately.
		/// </summary>
		/// 
		/// <param name="timeout">
		/// A TimeSpan that represents the number of milliseconds to wait, or a TimeSpan that represents 
		/// -1 milliseconds to wait indefinitely.</param>
		/// 
		/// <returns>
		/// True if the agent runtime has shut down within the allotted time; otherwise, False.</returns>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The agent runtime has not been started yet.</exception>
		public Boolean Wait(TimeSpan timeout)
		{
			if (mHasStarted == false)
				throw new InvalidOperationException(RuntimeResources.RuntimeNotStarted);

			return mShutdownCompletedSignal.WaitOne(timeout);
		}


		/// <summary>
		/// Starts the agent runtime and waits until the agent runtime has been shut down.
		/// 
		/// If the agent runtime has been started before or if the agent runtime has already been shut down, 
		/// then an appropriate exception will be thrown.
		/// </summary>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The agent runtime has already been started.</exception>
		/// <exception cref="InvalidOperationException">
		/// The agent runtime is completing or has already been completed.</exception>
		/// <exception cref="RuntimeStartException">
		/// The agent runtime could not be started. Refer to the inner exception for more information.</exception>
		public void Run()
		{
			this.Start();
			this.Wait();
		}

		#endregion

		#region ++ Public Interface (Publish) ++

		/// <summary>
		/// Publishes the supplied message and forwards it to interested consumers.
		/// 
		/// The method stores the supplied message in the dispatcher's input message queues of interested consumers and 
		/// returns immediately, meaning it only schedules the message for further processing, but the actual processing 
		/// is done later in other threads.
		/// 
		/// If there is no interested consumer at the time of publishing, then the message will be dropped silently.
		/// 
		/// Messages that are published after completing the publisher will be dropped silently.
		/// </summary>
		/// 
		/// <param name="message">
		/// The message that should be published.</param>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The agent runtime has not been started yet.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public void Publish(Message message)
		{
			if (mHasStarted == false)
				throw new InvalidOperationException(RuntimeResources.RuntimeNotStarted);

			mEnvironment.MessageBus.Publish(message);
		}

		/// <summary>
		/// Publishes the supplied message and forwards it to interested consumers.
		/// 
		/// The method stores the supplied message in the dispatcher's input message queues of interested consumers and 
		/// returns immediately, meaning it only schedules the message for further processing, but the actual processing 
		/// is done later in other threads.
		/// 
		/// If there is no interested consumer at the time of publishing, then the message will be dropped silently.
		/// 
		/// Messages that are published after completing the publisher will be dropped silently.
		/// </summary>
		/// 
		/// <param name="label">
		/// The label that should be assigned to the message before publishing.</param>
		/// <param name="message">
		/// The message that should be published.</param>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The agent runtime has not been started yet.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public void Publish(Object label, Message message)
		{
			if (mHasStarted == false)
				throw new InvalidOperationException(RuntimeResources.RuntimeNotStarted);

			mEnvironment.MessageBus.Publish(label, message);
		}

		#endregion

		#region ++ Public Interface (PublishOperation) ++

		/// <summary>
		/// Publishes the supplied message and forwards it to interested consumers.
		/// 
		/// The method stores the supplied message in the dispatcher's input message queues of interested consumers and 
		/// returns immediately, meaning it only schedules the message for further processing, but the actual processing 
		/// is done later in other threads.
		/// 
		/// If there is no interested consumer at the time of publishing, then the message will be dropped silently.
		/// 
		/// Messages that are published after completing the publisher will be dropped silently.
		/// </summary>
		/// 
		/// <typeparam name="TResult">
		/// The type of result value that is returned from the operation.</typeparam>
		/// <typeparam name="TProgress">
		/// The type of progress value that is used to report progress while the operation is running.</typeparam>
		/// 
		/// <param name="operation">
		/// The operation message that should be published.</param>
		/// <param name="progressAction">
		/// An optional callback that is called to handle progress values reported from the operation.</param>
		/// 
		/// <returns>
		/// A task that represents the asynchronously running operation. This task can be used to wait for completion, 
		/// to register a continuation and to access state and result of the operation.
		/// </returns>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The agent runtime has not been started yet.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Task<TResult> PublishOperation<TResult, TProgress>(
			Operation<TResult, TProgress> operation,
			Action<TProgress> progressAction = null)
		{
			if (mHasStarted == false)
				throw new InvalidOperationException(RuntimeResources.RuntimeNotStarted);

			return mEnvironment.MessageBus.PublishOperation(
				operation,
				progressAction);
		}

		/// <summary>
		/// Publishes the supplied message and forwards it to interested consumers.
		/// 
		/// The method stores the supplied message in the dispatcher's input message queues of interested consumers and 
		/// returns immediately, meaning it only schedules the message for further processing, but the actual processing 
		/// is done later in other threads.
		/// 
		/// If there is no interested consumer at the time of publishing, then the message will be dropped silently.
		/// 
		/// Messages that are published after completing the publisher will be dropped silently.
		/// </summary>
		/// 
		/// <typeparam name="TResult">
		/// The type of result value that is returned from the operation.</typeparam>
		/// <typeparam name="TProgress">
		/// The type of progress value that is used to report progress while the operation is running.</typeparam>
		/// 
		/// <param name="operation">
		/// The operation message that should be published.</param>
		/// <param name="cancellationToken">
		/// An CancellationToken that can be used to cancel the operation.</param>
		/// <param name="progressAction">
		/// An optional callback that is called to handle progress values reported from the operation.</param>
		/// 
		/// <returns>
		/// A task that represents the asynchronously running operation. This task can be used to wait for completion, 
		/// to register a continuation and to access state and result of the operation.
		/// </returns>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The agent runtime has not been started yet.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Task<TResult> PublishOperation<TResult, TProgress>(
			Operation<TResult, TProgress> operation,
			CancellationToken cancellationToken,
			Action<TProgress> progressAction = null)
		{
			if (mHasStarted == false)
				throw new InvalidOperationException(RuntimeResources.RuntimeNotStarted);

			return mEnvironment.MessageBus.PublishOperation(
				operation,
				cancellationToken,
				progressAction);
		}


		/// <summary>
		/// Publishes the supplied message and forwards it to interested consumers.
		/// 
		/// The method stores the supplied message in the dispatcher's input message queues of interested consumers and 
		/// returns immediately, meaning it only schedules the message for further processing, but the actual processing 
		/// is done later in other threads.
		/// 
		/// If there is no interested consumer at the time of publishing, then the message will be dropped silently.
		/// 
		/// Messages that are published after completing the publisher will be dropped silently.
		/// </summary>
		/// 
		/// <typeparam name="TResult">
		/// The type of result value that is returned from the operation.</typeparam>
		/// <typeparam name="TProgress">
		/// The type of progress value that is used to report progress while the operation is running.</typeparam>
		/// 
		/// <param name="label">
		/// The label that should be assigned to the message before publishing.</param>
		/// <param name="operation">
		/// The operation message that should be published.</param>
		/// <param name="progressAction">
		/// An optional callback that is called to handle progress values reported from the operation.</param>
		/// 
		/// <returns>
		/// A task that represents the asynchronously running operation. This task can be used to wait for completion, 
		/// to register a continuation and to access state and result of the operation.
		/// </returns>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The agent runtime has not been started yet.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Task<TResult> PublishOperation<TResult, TProgress>(
			Object label,
			Operation<TResult, TProgress> operation,
			Action<TProgress> progressAction = null)
		{
			if (mHasStarted == false)
				throw new InvalidOperationException(RuntimeResources.RuntimeNotStarted);

			return mEnvironment.MessageBus.PublishOperation(
				label,
				operation,
				progressAction);
		}

		/// <summary>
		/// Publishes the supplied message and forwards it to interested consumers.
		/// 
		/// The method stores the supplied message in the dispatcher's input message queues of interested consumers and 
		/// returns immediately, meaning it only schedules the message for further processing, but the actual processing 
		/// is done later in other threads.
		/// 
		/// If there is no interested consumer at the time of publishing, then the message will be dropped silently.
		/// 
		/// Messages that are published after completing the publisher will be dropped silently.
		/// </summary>
		/// 
		/// <typeparam name="TResult">
		/// The type of result value that is returned from the operation.</typeparam>
		/// <typeparam name="TProgress">
		/// The type of progress value that is used to report progress while the operation is running.</typeparam>
		/// 
		/// <param name="label">
		/// The label that should be assigned to the message before publishing.</param>
		/// <param name="operation">
		/// The operation message that should be published.</param>
		/// <param name="cancellationToken">
		/// An CancellationToken that can be used to cancel the operation.</param>
		/// <param name="progressAction">
		/// An optional callback that is called to handle progress values reported from the operation.</param>
		/// 
		/// <returns>
		/// A task that represents the asynchronously running operation. This task can be used to wait for completion, 
		/// to register a continuation and to access state and result of the operation.
		/// </returns>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The agent runtime has not been started yet.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Task<TResult> PublishOperation<TResult, TProgress>(
			Object label,
			Operation<TResult, TProgress> operation,
			CancellationToken cancellationToken,
			Action<TProgress> progressAction = null)
		{
			if (mHasStarted == false)
				throw new InvalidOperationException(RuntimeResources.RuntimeNotStarted);

			return mEnvironment.MessageBus.PublishOperation(
				label,
				operation,
				cancellationToken,
				progressAction);
		}

		#endregion

		#region ## Overridable Methods ##

		/// <summary>
		/// This method is called once during runtime startup to create the message bus instance. The method 
		/// can be overridden in derived types to return a custom message bus implementation. By default an 
		/// instance of <see cref="DefaultMessageBus"/> is returned.
		/// </summary>
		/// 
		/// <returns>
		/// A reference to the newly created message bus instance.</returns>
		protected virtual IMessageBus CreateMessageBus()
		{
			return new DefaultMessageBus();
		}

		/// <summary>
		/// This method is called once during runtime startup to create an instance of the agent runtime 
		/// environment, which will be provided to all subsystems and agents hosted in this agent runtime. 
		/// The method can be overridden in derived types to provide agents and subsystems with a custom 
		/// runtime environment that contains additional services.
		/// </summary>
		/// 
		/// <param name="runtime">
		/// A reference to the agent runtime, which must be passed to agent runtime environment 
		/// that will be returned.</param>
		/// <param name="messageBus">
		/// A reference to the message bus, which must be passed to the agent runtime environment
		/// that will be returned.</param>
		/// 
		/// <returns>
		/// A reference to the newly created agent runtime environment.</returns>
		protected abstract TEnvironment CreateEnvironment(IRuntime runtime, IMessageBus messageBus);


		/// <summary>
		/// An overrideable method that allows derived types to implement custom startup logic. The startup
		/// method is called once after the message bus and runtime environment have been created.
		/// 
		/// This method is guaranteed to be called once.
		/// </summary>
		protected virtual void OnStartup()
		{
			// default implementation
		}

		/// <summary>
		/// An overrideable method that allows derived types to implement custom cleanup logic. The cleanup
		/// method is called at last after the runtime run to completion.
		/// 
		/// This method is guaranteed to be called once.
		/// </summary>
		protected virtual void OnCleanup()
		{
			// default implementation
		}

		#endregion

		#region ## Protected Interface ##

		/// <summary>
		/// Initializes a new instance.
		/// </summary>
		protected Runtime()
		{
		}

		#endregion

		#region ## Protected Interface (LoadSubsystem) ##

		/// <summary>
		/// Loads the subsystem of the specified subsystem type.
		/// </summary>
		/// 
		/// <param name="subsystemType">
		/// The type of subsystem to load. This must be a sub-class of <see cref="Subsystem"/>.</param>
		/// <param name="environment">
		/// The runtime environment to be supplied to the subsystem.</param>
		/// 
		/// <returns>
		/// A reference to the newly loaded subsystem.</returns>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The agent runtime has not been started yet.</exception>
		/// <exception cref="InvalidOperationException">
		/// The agent runtime is completing or has already been completed.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="ArgumentException">
		/// A value was passed to a method that did not accept it as a valid argument, because 
		/// the value representing a type must be a sub-class of <see cref="Subsystem"/>.</exception>
		/// <exception cref="SubsystemLoadException">
		/// An instance of the specified subsystem type cannot be instantiated, because the subsystem type does not 
		/// provide a subsystem-specific constructor that accepts a runtime environment. Provide a constructor with 
		/// following signature: Subsystem(RuntimeEnvironment).</exception>
		/// <exception cref="SubsystemLoadException">
		/// An instance of the specified subsystem type could not be loaded. Refer to the inner exception for more 
		/// information.</exception>
		protected ISubsystem LoadSubsystem(Type subsystemType, RuntimeEnvironment environment)
		{
			return _RegisterSubsystem(
				() => Subsystem.Factory.LoadSubsystem(
					subsystemType,
					environment
			));
		}

		/// <summary>
		/// Loads the subsystem of the specified subsystem type.
		/// </summary>
		/// 
		/// <param name="subsystemType">
		/// The type of subsystem to load. This must be a sub-class of <see cref="Subsystem"/>.</param>
		/// 
		/// <returns>
		/// A reference to the newly loaded subsystem.</returns>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The agent runtime has not been started yet.</exception>
		/// <exception cref="InvalidOperationException">
		/// The agent runtime is completing or has already been completed.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="ArgumentException">
		/// A value was passed to a method that did not accept it as a valid argument, because 
		/// the value representing a type must be a sub-class of <see cref="Subsystem"/>.</exception>
		/// <exception cref="SubsystemLoadException">
		/// An instance of the specified subsystem type cannot be instantiated, because the subsystem type does not 
		/// provide a subsystem-specific constructor that accepts a runtime environment. Provide a constructor with 
		/// following signature: Subsystem(RuntimeEnvironment).</exception>
		/// <exception cref="SubsystemLoadException">
		/// An instance of the specified subsystem type could not be loaded. Refer to the inner exception for more 
		/// information.</exception>
		protected ISubsystem LoadSubsystem(Type subsystemType)
		{
			return _RegisterSubsystem(
				() => Subsystem.Factory.LoadSubsystem(
					subsystemType,
					mEnvironment
			));
		}


		/// <summary>
		/// Loads the subsystem of the specified subsystem type.
		/// </summary>
		/// 
		/// <typeparam name="TSubsystem">
		/// The type of subsystem to load. This must be a sub-class of <see cref="Subsystem"/>.</typeparam>
		/// 
		/// <param name="environment">
		/// The runtime environment to be supplied to the subsystem.</param>
		/// 
		/// <returns>
		/// A reference to the newly loaded subsystem.</returns>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The agent runtime has not been started yet.</exception>
		/// <exception cref="InvalidOperationException">
		/// The agent runtime is completing or has already been completed.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="SubsystemLoadException">
		/// An instance of the specified subsystem type cannot be instantiated, because the subsystem type does not 
		/// provide a subsystem-specific constructor that accepts a runtime environment. Provide a constructor with 
		/// following signature: Subsystem(RuntimeEnvironment).</exception>
		/// <exception cref="SubsystemLoadException">
		/// An instance of the specified subsystem type could not be loaded. Refer to the inner exception for more 
		/// information.</exception>
		protected ISubsystem LoadSubsystem<TSubsystem>(RuntimeEnvironment environment)
			where TSubsystem : Subsystem
		{
			return _RegisterSubsystem(
				() => Subsystem.Factory.LoadSubsystem<TSubsystem>(
					environment
			));
		}

		/// <summary>
		/// Loads the subsystem of the specified subsystem type.
		/// </summary>
		/// 
		/// <typeparam name="TSubsystem">
		/// The type of subsystem to load. This must be a sub-class of <see cref="Subsystem"/>.</typeparam>
		/// 
		/// <returns>
		/// A reference to the newly loaded subsystem.</returns>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The agent runtime has not been started yet.</exception>
		/// <exception cref="InvalidOperationException">
		/// The agent runtime is completing or has already been completed.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="SubsystemLoadException">
		/// An instance of the specified subsystem type cannot be instantiated, because the subsystem type does not 
		/// provide a subsystem-specific constructor that accepts a runtime environment. Provide a constructor with 
		/// following signature: Subsystem(RuntimeEnvironment).</exception>
		/// <exception cref="SubsystemLoadException">
		/// An instance of the specified subsystem type could not be loaded. Refer to the inner exception for more 
		/// information.</exception>
		protected ISubsystem LoadSubsystem<TSubsystem>()
			where TSubsystem : Subsystem
		{
			return _RegisterSubsystem(
				() => Subsystem.Factory.LoadSubsystem<TSubsystem>(
					mEnvironment
			));
		}


		/// <summary>
		/// Loads the subsystem of the specified subsystem type.
		/// </summary>
		/// 
		/// <typeparam name="TSubsystem">
		/// The type of subsystem to load. This must be a sub-class of <see cref="Subsystem"/>.</typeparam>
		/// <typeparam name="TSubsystemEnvironment">
		/// The type of runtime environment to supply. This must be a sub-class of <see cref="RuntimeEnvironment"/>.</typeparam>
		/// 
		/// <param name="environment">
		/// The runtime environment to be supplied to the subsystem.</param>
		/// 
		/// <returns>
		/// A reference to the newly loaded subsystem.</returns>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The agent runtime has not been started yet.</exception>
		/// <exception cref="InvalidOperationException">
		/// The agent runtime is completing or has already been completed.</exception>
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		/// <exception cref="SubsystemLoadException">
		/// An instance of the specified subsystem type cannot be instantiated, because the subsystem type does not 
		/// provide a subsystem-specific constructor that accepts a runtime environment. Provide a constructor with 
		/// following signature: Subsystem(RuntimeEnvironment).</exception>
		/// <exception cref="SubsystemLoadException">
		/// An instance of the specified subsystem type could not be loaded. Refer to the inner exception for more 
		/// information.</exception>
		protected ISubsystem LoadSubsystem<TSubsystem, TSubsystemEnvironment>(TSubsystemEnvironment environment)
			where TSubsystem : Subsystem
			where TSubsystemEnvironment : RuntimeEnvironment
		{
			return _RegisterSubsystem(
				() => Subsystem.Factory.LoadSubsystem<TSubsystem, TSubsystemEnvironment>(
					environment
			));
		}

		#endregion

		#region Implementation

		/// <summary>
		/// This method supports the agent runtime infrastructure and is not intended to be used directly.
		/// </summary>
		protected override Task OnComplete(Boolean discardAvailableMessages)
		{
			var taskCompletionSource = new TaskCompletionSource();
			var task = (Task)null;

			// get copy of subsystems
			var subsystems = mSubsystems.ToArray();
			mSubsystems.Clear();

			if (discardAvailableMessages)
			{
				// complete in parallel
				foreach (var subsystem in subsystems)
					subsystem.Complete(true);

				var completionTasks = subsystems.Select(
					item => item.Completion);

				task = TaskHelper.WhenAll(completionTasks);
			}
			else
			{
				task = Task.Factory.StartNew(() =>
					{
						// complete sequentially in reverse order
						foreach (var subsystem in subsystems.Reverse<ISubsystem>())
						{
							subsystem.Complete(false);
							subsystem.Completion.WaitQuietly();
						}
					})
					.IgnoreExceptions();
			}

			task.ContinueWith(t =>
				{
					if (mEnvironment == null || mEnvironment.MessageBus == null)
					{
						taskCompletionSource.SignalCompletion();
					}
					else
					{
						// complete message bus
						this.Environment.MessageBus.Complete(discardAvailableMessages);
						this.Environment.MessageBus.Completion.ContinueWith(t1 =>
							{
								taskCompletionSource.SignalCompletion();
							},
							TaskContinuationOptions.ExecuteSynchronously);
					}
				},
				TaskContinuationOptions.ExecuteSynchronously);

			return taskCompletionSource.Task;
		}

		/// <summary>
		/// This method supports the agent runtime infrastructure and is not intended to be used directly.
		/// </summary>
		protected override void OnCompleted()
		{
			try
			{
				// call sub-class
				OnCleanup();
			}
			catch (Exception exception)
			{
				#region (tracing)
				{
					Trace.WriteLine(String.Format(CultureInfo.InvariantCulture,
						"*** WARNING: UNHANDLED EXCEPTION in Runtime.OnCleanup():" +
						"\r\n  ObjectId: {0}" +
						"\r\n\r\n{1}",
						base.ObjectId,
						Helper.FormatException(exception, CultureInfo.InvariantCulture)
					));
				}
				#endregion
			}
			finally
			{
				// signal that shutdown has been completed
				mShutdownCompletedSignal.Set();
			}
		}

		private ISubsystem _RegisterSubsystem(Func<ISubsystem> func)
		{
			if (mHasStarted == false)
				throw new InvalidOperationException(RuntimeResources.RuntimeNotStarted);
			if (base.IsCompleting)
				throw new InvalidOperationException(RuntimeResources.RuntimeCompletingOrCompleted);

			var subsystem = func();
			mSubsystems.Push(subsystem);
			return subsystem;
		}

		#endregion

	}
}
